Dataset Readme(Sample)
Wrangled Data
서울특별시 데이터셋
by: 서용훈
※ 본 문서는 PC 환경에 최적화되어있습니다.
1 목차
구상 중입니다.
2 데이터셋 위치
모든 데이터셋은 ‘./data/Datasets/’ 디렉터리 안에 있습니다.
3 정제 데이터셋
데이터셋 랭글링 과정을 담았습니다.
3.1 행정경계 데이터 불러오기
먼저, 분석 스케일의 뼈대가 되는 행정경계 데이터를 불러왔습니다.
3.1.1 사용한 패키지
3.1.2 구
전국 시군구 파일에서 시군구 코드를 기준으로 서울 지역만 추출하는 과정입니다.
seoulgu <- readOGR(dsn = "./data/shp/구",
layer = "kr_si_gun-gu",
encoding = "UTF-8",
stringsAsFactors = FALSE,
verbose = FALSE) %>%
subset(SIG_CD <= 11740)
#seouldong <- readOGR(dsn = "./data/shp/행정동",
# layer = "HangJeongDong_ver20200401.ㅓ",
# encoding = "UTF-8",
# stringsAsFactors = FALSE,
# verbose = FALSE)# %>% subset(SIG_CD < 20000)
#sqlseoul <- "SELECT * FROM As features WHERE 'adm_cd' < '20000'"
#seouldong <- geojson_read("./data/shp/행정동/HangJeongDong_ver20200401.geojson", parse = FALSE, what = "sp", stringsAsFactors = FALSE)[c(1:425),]3.1.3 행정동
좌표투영이 되어있지 않은 파일이라 구득 웹 사이트(국가공간정보포털 오픈마켓) 상에서 메타데이터를 확인하여, 지리원 표준 좌표계 중 하나인 ‘EPSG:5181’(중부원점, GRS80)로 좌표계를 정의해주고, 불러옴과 동시에 서울 행정동만 추출하였습니다.
3.2 서울시 인구
서울시 인구 자료를 정제하였으며, 2020년 1/4분기 자료입니다.
3.2.1 사용한 패키지
3.2.2 자료의 출처
해당 자료는 서울시 주민등록인구 (구별) 통계 및 서울시 주민등록인구 (동별) 통계에서 구득하였습니다.
3.2.3 정제 과정 - 구
먼저 앞서 언급한 웹 페이지에서 구득한 구분자(Delimiter)로 구성된 파일을 불러왔습니다.
추후에 셰이프 파일 형식으로도 사용할 수 있게 필드 최대 허용 바이트 수를 넘지 않는 선에서 열 이름을 변경하였습니다.
names(pop.gu) <- c("quarter","GU","HousHld","TotlPop","Male","Female","Domestc",
"DomMale","DomFmle","Foreign","ForMale","ForFmle","cap/hhd","65+")반복문과 조건문을 사용하여 문자열 열을 수치형 열로 바꿔주는 작업입니다.
천 단위마다 찍히는 콤마를 제거하고, 문자형 열을 제외한 나머지 열만 수치형으로 변환하였습니다.
for(i in 1:length(pop.gu)){
for(j in 1:length(pop.gu$`cap/hhd`)){
pop.gu[j,i] <- pop.gu[j,i] %>% str_remove(',')
}
if(i>2){
pop.gu[ , i] <- apply(pop.gu[ , i], 2,
function(x) as.numeric(as.character(x)))
}
}정제 결과물입니다.
위의 결과물을 서울시 구 공간 데이터에 속성정보로 넣었습니다.
3.2.4 정제 과정 - 동
구에서 정제한 방법과 유사하게 진행하였으며, 먼저 구분자(Delimiter)로 구성된 파일을 불러왔습니다.
추후에 셰이프 파일 형식으로도 사용할 수 있게 필드 최대 허용 바이트 수를 넘지 않는 선에서 열 이름을 변경하였습니다.
names(pop.dong) <- c("quarter","GU","EMD","HousHld","TotlPop","Male","Female","Domestc",
"DomMale","DomFmle","Foreign","ForMale","ForFmle","cap/hhd","65+")반복-조건문을 사용하여 문자형을 수치형으로 변환하였고, 인구자료에는 시군구 코드가 없는 관계로 공간자료 속성정보에 조인(join) 대상을 맞춰주는 작업을 진행하였습니다. 아래는 온점(.)을 가운뎃점(·)으로 변환하는 과정입니다.
for(i in 1:length(pop.dong)){
for(j in 1:length(pop.dong$`cap/hhd`)){
pop.dong[j,i] <- pop.dong[j,i] %>% str_remove(',')
}
if(i>3){
pop.dong[ , i] <- apply(pop.dong[ , i], 2,
function(x) as.numeric(as.character(x)))
}
if(i==3){
for(j in 1:length(pop.dong$`cap/hhd`)){
pop.dong[j,i] <- pop.dong[j,i] %>% str_replace_all(pattern ="\\.",replacement = "·")
}
}
}아래 과정도 조인 대상을 맞춰주는 과정으로, 중복된 동 이름을 알맞게 변환하는 과정입니다.
for(j in 1:length(pop.dong$EMD)){
if(pop.dong$GU[j] == "관악구" && pop.dong$EMD[j] == "신사동"){
pop.dong$EMD[j] <- "신사동(관악)"
}else if(pop.dong$GU[j] == "강남구" && pop.dong$EMD[j] == "신사동"){
pop.dong$EMD[j] <- "신사동(강남)"
}
}다음은 공간자료의 속성정보로 넣어주는 과정입니다.
seoulemd.pop <- merge(seoulemd, pop.dong ,by.x ="ADM_DR_NM", by.y = "EMD")
tm_shape(seoulemd.pop) + tm_polygons("TotlPop")위에서 조인을 하면서 자연스럽게 구마다 존재하는 소계 항목(행)을 자연스럽게 제거한 결과물입니다.
3.2.5 정제 자료 저장
다음과 같이 ‘./data/Datasets/Population/’ 디렉터리에 데이터프레임을 저장하였고, ‘./data/Datasets/Population/shapefile/’ 디렉터리에 공간정보 또한 셰이프파일 포멧으로 저장하였습니다. 셰이프 파일 저장시의 for문은 저장시 위에서 변환한 수치형의 필드를 오류가 발생하지 않도록 다시 한 번 명시하는 과정입니다.
# 서울시 구별 인구 데이터프레임
write_csv(pop.gu, path = "./data/Datasets/Population/20201stQGu.csv", append = FALSE)
# 서울시 동별 인구 데이터프레임
write_csv(seoulemd.pop@data, path = "./data/Datasets/Population/20201stQDong.csv", append = FALSE)
# 서울시 구별 인구 공간정보(폴리곤, 구)
for(i in 5:16){
#print(i)
seoulgu.pop@data[,i] <- as.numeric(seoulgu.pop@data[,i])
}
writeOGR(seoulgu.pop, paste0(getwd(), "/data/Datasets/Population/shapefile"), "20201stQGu", driver = "ESRI Shapefile",
layer_options = "encoding=UTF-8", overwrite_layer = TRUE)
# 서울시 동별 인구 공간정보(폴리곤, 행정동)
for(i in 6:17){
#print(i)
seoulemd.pop@data[,i] <- as.numeric(seoulemd.pop@data[,i])
}
writeOGR(seoulemd.pop, paste0(getwd(), "/data/Datasets/Population/shapefile"), "20201stQDong", driver = "ESRI Shapefile",
layer_options = "encoding=UTF-8", overwrite_layer = TRUE)3.3 올리브영 매장
3.3.1 사용한 패키지
3.3.2 자료의 출처
해당 문서에서 정제한 올리브영 자료는 2020년 7월 19일 기준입니다.
해당 자료는 올리브영 매장안내에서 구득하였습니다.
올리브영 웹 페이지에 게시된 서울시 매장 목록을 사용할 목적으로 html 크롤링을 시도하였으나
위와 같이 스크롤을 모든 항목이 나타날 때까지 해야 되므로, 아래와 같이
스크롤을 하여 모든 항목을 불러온 뒤, 해당 요소를 ’Notepad++’을 사용하여 별도의 html 파일을 생성하였습니다.
3.3.3 정제 과정
먼저, 생성한 html을 불러왔습니다.
불러온 html에서 ‘매장명’과 ’주소’, ‘전화번호’, ’관심매장등록수’를 추출한 뒤, 하나의 데이터프레임을 만들었습니다.
library(tidyverse)
name <- html_nodes(html.olive, css="a") %>%
str_remove('<a href="javascript:;">') %>%
str_remove('</a>')
addr <- html_nodes(html.olive, css=".addr") %>%
str_remove('<p class="addr">') %>%
str_remove('</p>')
ph.numb <- html_nodes(html.olive, css=".call") %>%
str_remove('<div class="call">') %>%
str_remove('</div>')
noi <- html_nodes(html.olive, css=".fv_reShop_in") %>%
html_nodes("span") %>%
as_list() %>%
unlist(use.names=FALSE) %>%
str_remove(',')
olive <- cbind(name,addr,ph.numb,noi) %>%
as.data.frame(stringsAsFactors=FALSE)
olive$noi <- as.integer(olive$noi)
head(olive) name addr
1 은행사거리점 서울특별시 노원구 한글비석로 264 중계그랜드프라자 1층
2 중계역점 서울특별시 노원구 동일로 1335 (상계동)
3 노원역사거리점 서울특별시 노원구 노해로 480
4 노원점 서울특별시 노원구 상계로 65 105호,106호
5 홈플러스중계점 서울특별시 노원구 동일로204가길 12 홈플러스중계점 1층
6 상계보람점 서울특별시 노원구 한글비석로 471
ph.numb noi
1 02-938-9305 1278
2 02-930-2952 518
3 02-934-5123 2365
4 02-935-5290 1948
5 02-948-6960 526
6 02-930-2532 729
위 데이터프레임을 바로 뒤에 언급할 지오코딩 툴을 사용하여 잘못된 주소를 바로잡았습니다.
olive$addr[110] <- as.character("서울특별시 강동구 천호대로 997 지하 1층, 천호엔터식스 012호")
olive$addr[113] <- as.character("서울 종로구 송월길 99 경희궁자이 202동 2132호")
olive$addr[142] <- as.character("만리동2가 229-1")
olive$addr[156] <- as.character("서울 강남구 가로수길 5 1층 올리브영 가로수길입구점")
olive$addr[219] <- as.character("서울특별시 서초구 반포동 19-11")
olive$addr[290] <- as.character("서울특별시 동작구 동작대로 3")
olive$addr[368] <- as.character("서울특별시 강동구 성내로 89")
olive$name[368] <- as.character("티지에스플러스(준비중)")다음의 표에서 검색도 가능합니다.
3.3.4 지오코딩
네이버 클라우드에서 제공하는 지오코딩 API 서비스를 사용하여 좌표를 부여하였습니다.
- 네이버 클라우드
앞서 언급하였듯이, 지오코딩 루프를 돌리면서 주소를 통하여 좌표를 잡지 못하는 데이터를 수정하는 방식으로 주소 무결성 또한 확인하였습니다.
library(httr)
library(XML)
#Client ID 설정
clientId <- "k8g52bouny"
#Client Secret 설정
clientSecret <- "Kadl4ZjbFOhhXXk7806OA24h7sdSEaDYuczHAgaD"
x <- vector(mode = "double", length(olive$addr))
y <- vector(mode = "double", length(olive$addr))
for (i in 1:length(olive$addr)){
#print(i)
apiResult <- httr::GET(
url = "https://naveropenapi.apigw.ntruss.com/map-geocode/v2/geocode",
httr::add_headers(
`X-NCP-APIGW-API-KEY-ID` = clientId,
`X-NCP-APIGW-API-KEY` = clientSecret
),
query = list(
`query` = olive$addr[i]
)
)
if (apiResult$status_code == "200"){
#print("ResultCode OK!")
result <- rawToChar(apiResult$content)
Encoding(result) <- "UTF-8"
list <- xmlToList(result)
u <- as.numeric(list$addresses$x)
v <- as.numeric(list$addresses$y)
x[i] <- u
y[i] <- v
}else if (apiResult$status_code == "400"){
print(paste("Bad Request Exception in",olive$name[i]))
}else if (apiResult$status_code == "500"){
print(paste("Unexpected Error in",olive$name[i]))
}else{
print("몰라요")
}
}
olive$x <- x
olive$y <- y3.3.5 자료의 공간정보화
먼저 GCS(WGS84)를 사용하여 지오코딩으로 획득한 좌표를 사용하여 공간정보로 변환하였습니다. 또한 행정경계 파일과 좌표체계를 통일하여 공간연산이 가능하게 하였습니다.
3.3.6 공간연산
구, 행정동당 올리브 영의 수를 연산하였습니다.
library(GISTools)
num.gu <- poly.counts(oliveSP, seoulgu)
num.emd <- poly.counts(oliveSP, seoulemd)
head(num.gu) 0 1 2 3 4 5
15 22 12 11 14 8
0 1 2 3 4 5
1 2 0 1 0 1
위에서 연산한 1차 배열의 자료를 두 행정구역 자료의 속성 테이블에 각각 넣어주었습니다.
3.3.7 올리브영 매장 분포 시각화
올리브영 매장 분포
공간연산(구)
3.3.8 정제 자료 저장
다음과 같이 ‘./data/Datasets/Oliveyoung/’ 디렉터리에 데이터프레임을 저장하였고, ‘./data/Datasets/Oliveyoung/shapefile/’ 디렉터리에 공간정보 또한 셰이프파일 포멧으로 저장하였습니다.
# 올리브영 데이터프레임
write_csv(olive, path = "./data/Datasets/Oliveyoung/Oliveyoung.csv", append = FALSE)
# 올리브영 셰이프(점)
writeOGR(oliveSP, paste0(getwd(), "/data/Datasets/Oliveyoung/shapefile"), "Oliveyoung", driver = "ESRI Shapefile",
layer_options = "encoding=UTF-8", overwrite_layer = TRUE)
# 올리브영 점포 수 셰이프(폴리곤, 구)
writeOGR(seoulgu, paste0(getwd(), "/data/Datasets/Oliveyoung/shapefile"), "OliveGu", driver = "ESRI Shapefile",
layer_options = "encoding=UTF-8", overwrite_layer = TRUE)
#올리브영 점포 수 셰이프(폴리곤, 행정동)
writeOGR(seoulemd, paste0(getwd(), "/data/Datasets/Oliveyoung/shapefile"), "OliveEMD", driver = "ESRI Shapefile",
layer_options = "encoding=UTF-8", overwrite_layer = TRUE)
#test <- read_csv("./data/Datasets/Oliveyoung/Oliveyoung.csv")